/*
 * @Author: wangcb
 * @Email: wangcbgg@163.com
 * @Date: 2021-10-21 17:05:52
 * @LastEditTime: 2021-10-21 17:52:56
 * 抽帧预览
 */
import cancelAFrame from "../helpers/cancelAFrame";
import requestAFrame from "../helpers/requestAFrame";
import { $ } from '../helpers/selector';
const mask = new Image();
mask.src = '';
let throttleTimer = 0; // 节流
/**
 * 函数节流
 * @private
 */
const throttle = (fn, delay = 1000) => {
    if (!throttleTimer) {
        throttleTimer = setTimeout(() => {
            clearTimeout(throttleTimer), throttleTimer = 0, fn();
        }, delay);
    }
};
/**
 * 抽帧预览类
 * @class FrameView
 */
class FrameView {
    constructor(config) {
        /**
         * 预览索引范围
         * @private
         * @type {Array<number>}
         */
        this.seekRange = [-4, -3, -2, -1, 0, 1, 2, 3, 4];
        /**
         * 预览图片数组
         * @private
         * @type {Array<string>}
         */
        this.previews = [];
        /**
         * 预览框dom节点
         * @type {HTMLElement}
         */
        this.domNode = document.body;
        /**
         * 预览时间dom节点
         * @type {HTMLElement | null}
         */
        this.timeNode = null;
        /**
         * 预览配置
         * @type {IPreviewConfig}
         */
        this.config = { domNode: this.domNode };
        /**
         * canvas绘图上下文
         * @private
         * @type {(CanvasRenderingContext2D | null)}
         */
        this.ctx = null;
        /**
         * canvas长
         * @type {number}
         */
        this.canvasWidth = 1920;
        /**
         * canvas高
         * @private
         * @type {number}
         */
        this.canvasHeight = 300;
        /**
         * 大图中的帧列数
         * @type {number}
         */
        this.frameCol = 5;
        /**
         * 大图中的帧行数
         * @type {number}
         */
        this.frameRow = 5;
        /**
         * 预览帧数组
         * @private
         * @type {Array<IFrameData>}
         */
        this.frameList = [];
        /**
         * 当前进入预览的帧数组
         * @private
         * @type {Array<IFrameData>}
         */
        this.seekFrameList = [];
        /**
         * 一帧的长度
         * @type {number}
         */
        this.frameWidth = 289;
        /**
         * 一帧的高度
         * @type {number}
         */
        this.frameHeight = 171;
        /**
         * 焦点帧长度
         * @private
         * @type {number}
         */
        this.focusFrameWidth = 347;
        /**
         * 焦点帧高度
         * @private
         * @type {number}
         */
        this.focusFrameHeight = 206;
        /**
         * 一帧小图的长度
         * @private
         * @type {number}
         */
        this.pictureWidth = 360;
        /**
         * 一帧小图的高度
         * @private
         * @type {number}
         */
        this.pictureHeight = 204;
        /**
         * 焦点帧的缩放比例
         * @type {number}
         */
        this.focusScale = 1.25;
        /**
         * 一帧的时间步长
         * @private
         * @type {number}
         */
        this.frameStep = 10;
        /**
         * 帧左右间隔
         * @type {number}
         */
        this.frameGap = 20;
        /**
         * 焦点帧左右间距
         * @type {number}
         */
        this.focusGap = 25;
        /**
         * 最后一帧时间
         * @private
         * @type {number}
         */
        this.lastFrameTime = 0;
        /**
         * 片长
         * @type {number}
         */
        this.duration = 0;
        /**
         * 当前时间点
         * @private
         * @type {number}
         */
        this.currentTime = 0;
        /**
         * 移动方向(1 右移 -1 左移)
         * @private
         * @type {number}
         */
        this.direction = 1;
        /**
         * 缓动步长
         * @type {number}
         */
        this.easing = 0.3;
        /**
         * 水平中心x偏移值
         * @private
         * @type {number}
         */
        this.centerX = 0;
        /**
         * 动画帧id
         * @private
         * @type {number}
         */
        this.requestId = 0;
        /**
         * 显示的文字尺寸
         * @type {number}
         */
        this.fontSize = 28;
        /**
         * 字体的颜色
         * @type {string}
         */
        this.color = '#fff';
        if (config) {
            this.config = config;
            if (config.previews)
                this.previews = config.previews;
            if (config.domNode)
                this.domNode = config.domNode;
            if (config.canvasWidth)
                this.canvasWidth = config.canvasWidth;
            if (config.frameCol)
                this.frameCol = config.frameCol;
            if (config.frameRow)
                this.frameRow = config.frameRow;
            if (config.frameWidth)
                this.frameWidth = config.frameWidth;
            if (config.frameHeight)
                this.frameHeight = config.frameHeight;
            if (config.focusScale)
                this.focusScale = config.focusScale;
            if (config.frameGap)
                this.frameGap = config.frameGap;
            if (config.focusGap)
                this.focusGap = config.focusGap;
            if (config.duration)
                this.setDuration(config.duration);
            if (config.easing)
                this.easing = config.easing;
            if (config.fontSize)
                this.fontSize = config.fontSize;
            if (config.color)
                this.color = config.color;
            if (config.animate)
                this.$setAnimate();
            this.focusFrameWidth = this.frameWidth * this.focusScale;
            this.focusFrameHeight = this.frameHeight * this.focusScale;
            this.focusGap *= this.focusScale;
            this.centerX = this.focusFrameWidth / 2 + this.focusGap + this.frameWidth / 2;
            this.previews && this.previews.length > 0 && this.$createDom();
            if (config.zteData)
                this.$createDom();
        }
    }
    /**
     * 设置动画样式
     * @private
     */
    $setAnimate() {
        const head = document.getElementsByTagName('head')[0];
        const animateId = '__preview-animate__';
        if (!$(animateId)) {
            const style = document.createElement('style');
            style.type = 'text/css', style.id = animateId;
            const css = '.__preview-active__{animation-name:debance;-webkit-animation-name:debance;animation-duration:250ms;-webkit-animation-duration:250ms;}@keyframes debance{0%{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0);}90%{transform:translate3d(30px,0,0);-webkit-transform:translate3d(30px,0,0);}100%{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0);}}';
            if ('styleSheet' in style) {
                // @ts-ignore IE适配
                style.setAttribute('type', 'text/css');
                // @ts-ignore IE适配
                style.styleSheet.cssText = css;
            }
            else {
                style.innerHTML = css;
            }
            head.appendChild(style);
        }
    }
    /**
     * 中兴数据专用
     * @private
     * @param {ZteData} zteData
     */
    $zteDataHandler(zteData) {
        const imageList = zteData?.thumbnails;
        if (!imageList)
            return null;
        const width = +zteData.width;
        const height = +zteData.height;
        const step = +zteData.timeinterval;
        const result = [];
        for (let i = 0; i < imageList.length; i++) {
            const item = imageList[i];
            const url = item.templateurl;
            const segments = item.segments;
            const startTime = segments[0].time, endTime = segments[segments.length - 1].time;
            result.push({
                url, width, height, step, total: segments.length,
                startTime, endTime
            });
        }
        result.sort((p, n) => p.startTime - n.startTime);
        return result;
    }
    /**
     * 数据处理
     * @private
     * @param {Array<string>} data
     */
    $dataFilter(data) {
        const result = [];
        for (let i = 0; i < data.length; i++) {
            const url = data[i] + '';
            const matchData = url.match(/_360(?:_\d+){5}/);
            if (matchData) {
                const dataArr = matchData[0].split('_');
                result.push({
                    url, width: +dataArr[1], height: +dataArr[2],
                    step: +dataArr[3], total: +dataArr[4],
                    startTime: +dataArr[5], endTime: +dataArr[6]
                });
            }
        }
        result.sort((p, n) => p.startTime - n.startTime);
        return result;
    }
    /** 加载画布 */
    $loadPaper() {
        const paperClass = '__preview-canvas__';
        const paper = $(paperClass);
        if (!this.domNode.contains(paper)) {
            const canvas = document.createElement('canvas');
            canvas.id = paperClass;
            canvas.width = this.canvasWidth;
            canvas.height = this.canvasHeight = this.focusFrameHeight + 100;
            this.ctx = canvas.getContext('2d');
            this.domNode.appendChild(canvas);
            this.domNode.style.position = 'absolute';
        }
        else {
            this.ctx = paper.getContext('2d');
            this.$clearCanvas();
        }
    }
    /** 加载time元素 */
    $loadTimeDom() {
        const timeClass = '__preview-time__';
        const time = $(timeClass);
        if (!this.domNode.contains(time)) {
            this.timeNode = document.createElement('div');
            this.timeNode.id = timeClass;
            this.timeNode.style.cssText = `
        width:${this.focusFrameWidth}px;
        margin-left:-${this.focusFrameWidth / 2}px;
        text-align:center;position:absolute;left:50%;
        top:${this.focusFrameHeight + 10}px;
        font-size:${this.fontSize}px;
        color:${this.color};user-select:none;
      `;
            this.domNode.appendChild(this.timeNode);
        }
        else {
            this.timeNode = time;
            if (this.timeNode)
                this.timeNode.innerHTML = '';
        }
    }
    /**
     * 构建预览dom
     * @private
     */
    $createDom() {
        const data = this.config.zteData ? this.$zteDataHandler(this.config.zteData) : this.$dataFilter(this.previews);
        if (data?.length) {
            this.pictureWidth = data[0].width;
            this.pictureHeight = data[0].height;
            this.frameStep = data[0].step;
            this.lastFrameTime = data[data.length - 1].endTime;
            for (let i = 0; i < data.length; i++) {
                const item = data[i];
                const img = new Image();
                img.src = item.url;
                for (let j = 0; j < item.total; j++) {
                    this.frameList.push({
                        img: img,
                        sx: j % this.frameCol * this.pictureWidth + 2,
                        sy: this.config.zteData ? 0 : Math.floor(j / this.frameRow) * this.pictureHeight + 2,
                        sWidth: this.pictureWidth - 4,
                        sHeight: this.pictureHeight - 4,
                        startTime: item.startTime + j * this.frameStep,
                        endTime: item.startTime + (j + 1) * this.frameStep
                    });
                }
            }
        }
        this.$loadPaper(), this.$loadTimeDom();
    }
    /**
     * 清空画布
     * @private
     */
    $clearCanvas() {
        this.ctx && this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
    }
    /**
     * 绘制frame
     * @private
     * @param {boolean} fixFocus 修复焦点帧尺寸
     */
    $drawFrame(fixFocus) {
        this.$clearCanvas(); // 清空画布
        // 当前时间点在frameList中的索引
        const currentIndex = Math.round(this.currentTime / this.frameStep);
        this.seekFrameList = []; // 进入预览的frame
        const radius = this.focusFrameWidth / 2 + this.focusGap; // 焦点帧的偏移半径
        for (let i = 0; i < this.seekRange.length; i++) {
            const index = this.seekRange[i]; // 预览范围索引
            const frame = this.frameList[currentIndex + index]; // 预览帧
            if (frame && frame.startTime <= this.duration) {
                let dx, dy, direction, dWidth = this.frameWidth, dHeight = this.frameHeight;
                dy = (this.canvasHeight - this.frameHeight) / 2; // y轴偏移量
                if (index === 0) {
                    if (fixFocus) {
                        dx = (this.canvasWidth - this.focusFrameWidth) / 2;
                        dy = (this.canvasHeight - this.focusFrameHeight) / 2;
                        dWidth = this.focusFrameWidth, dHeight = this.focusFrameHeight;
                    }
                    else {
                        dx = (this.canvasWidth - this.frameWidth) / 2;
                    }
                }
                else {
                    direction = Math.abs(index) / index;
                    dx = this.canvasWidth / 2 + direction * (radius + (index > 0 ? index - 1 : Math.abs(index)) * this.frameWidth + (Math.abs(index) - 1) * this.frameGap);
                }
                if (0 === index || -1 === this.direction && -1 === index || 1 === this.direction && 1 === index) {
                    frame.offsetX = this.centerX;
                }
                else {
                    frame.offsetX = this.frameWidth + this.frameGap;
                }
                frame.dx = dx, frame.dy = dy;
                this.seekFrameList.push(frame);
                try {
                    this.ctx && this.ctx.drawImage(frame.img, frame.sx, frame.sy, frame.sWidth, frame.sHeight, dx, dy, dWidth, dHeight);
                }
                catch (error) {
                    console.log(error);
                }
            }
            else {
                this.seekFrameList.push(null);
            }
        }
        this.$drawFocusAndTime();
    }
    /**
     * frame滑动动效
     * @private
     */
    $translateFrame(callback) {
        const render = () => {
            this.$clearCanvas(); // 清空画布
            let outCondition; // 出口值
            for (let i = 0; i < this.seekFrameList.length; i++) {
                const frame = this.seekFrameList[i]; // 预览帧
                if (frame && frame.startTime <= this.duration) {
                    var dWidth, dHeight, stepX = frame.offsetX * this.easing; // 缓动步长
                    if (3 === i && this.direction < 0 || 5 === i && this.direction > 0) { // 锁定焦点帧位置
                        this.$zoomFrame(frame, stepX / this.centerX, frame.offsetX / this.centerX);
                        dWidth = frame.dWidth, dHeight = frame.dHeight;
                    }
                    else {
                        dWidth = this.frameWidth, dHeight = this.frameHeight;
                    }
                    outCondition = frame.offsetX = frame.offsetX - stepX;
                    frame.dx += stepX * this.direction * -1;
                    try {
                        this.ctx && this.ctx.drawImage(frame.img, frame.sx, frame.sy, frame.sWidth, frame.sHeight, frame.dx, frame.dy, dWidth, dHeight);
                    }
                    catch (error) {
                        console.log(error);
                    }
                }
            }
            this.$drawFocusAndTime();
            outCondition && outCondition > 0.5 ? this.requestId = requestAFrame(render) : callback();
        };
        render();
    }
    /**
     * 缩放焦点帧
     * @private
     * @param {IFrameData} frame 帧对象
     * @param {number} step 距焦点帧中心的比率
     * @param {number} offset 偏移率
     */
    $zoomFrame(frame, step, offset) {
        const sx = (this.focusFrameWidth - this.frameWidth) / 2;
        const sy = (this.focusFrameHeight - this.frameHeight) / 2;
        frame.dx = frame.dx - sx * step, frame.dy = frame.dy - sy * step;
        frame.dWidth = this.frameWidth + 2 * sx * (1 - offset);
        frame.dHeight = this.frameHeight + 2 * sy * (1 - offset);
    }
    /**
     * 参数补0
     * @private
     * @param {number} arg
     * @returns {string} 处理好的参数
     */
    $addZeros(arg) {
        return arg < 10 ? '0' + arg : arg + '';
    }
    /**
     * 转换播放时间
     * @private
     * @param {number} time 待转换时间
     * @returns {string} 处理好的播放时间
     */
    $convertPlayTime(time) {
        const hour = this.$addZeros(Math.floor(time / 3600));
        const minute = this.$addZeros(Math.floor(time % 3600 / 60));
        const second = this.$addZeros(time % 3600 % 60);
        return `${hour} : ${minute} : ${second}`;
    }
    /**
     * 绘制焦点框和时间节点
     * @private
     */
    $drawFocusAndTime() {
        if (this.ctx) {
            const gap = 24 * this.focusScale;
            const x = (this.canvasWidth - this.focusFrameWidth) / 2;
            const y = (this.canvasHeight - this.focusFrameHeight) / 2;
            this.ctx.drawImage(mask, 0, 0, 404, 263, x - gap, y - gap, this.focusFrameWidth + 2 * gap, this.focusFrameHeight + 2 * gap);
            const time = this.$convertPlayTime(this.currentTime || 0);
            if (this.timeNode && this.timeNode.innerHTML !== time)
                this.timeNode.innerHTML = time;
        }
    }
    /**
     * 开始绘制
     * @private
     * @param {number} direction 移动方向(1 右移 -1 左移)
     */
    $render(direction) {
        this.direction = direction;
        cancelAFrame(this.requestId);
        this.$drawFrame(false);
        this.$translateFrame(() => this.$drawFrame(true));
    }
    /**
     * 设置片长
     * @param {number} duration 片长(单位s)
     */
    setDuration(duration) {
        this.duration = duration;
    }
    /**
     * 设置一帧的时间步长
     * @param {number} frameStep
     */
    setFrameStep(frameStep) {
        this.frameStep = frameStep;
    }
    /**
     * 设置当前播放时间点
     * @param {number} currentTime
     */
    setPlayTime(currentTime) {
        this.currentTime = currentTime;
    }
    /**
     * 获取当前播放时间
     * @returns {number} 当前播放时间(单位s)
     */
    getPlayTime() {
        return this.currentTime;
    }
    /**
     * seek抽帧预览
     * @param {number} direction 移动方向(1 右移 -1 左移)
     */
    seek(direction) {
        if (this.config.zteData?.thumbnails?.length || this.previews?.length) {
            if (this.config.animate && (direction < 0 && this.currentTime <= 0) || (direction > 0 && this.currentTime >= this.lastFrameTime - this.frameStep)) {
                this.domNode.classList.add('__preview-active__');
                throttle(() => {
                    this.domNode.classList.remove('__preview-active__');
                }, 250);
            }
            const mod = this.currentTime % this.frameStep;
            if (mod !== 0) { // 确保每次的移动都是一帧
                if (direction < 0) {
                    this.currentTime -= mod;
                }
                else {
                    if (this.currentTime > this.lastFrameTime) {
                        this.currentTime = this.lastFrameTime;
                        cancelAFrame(this.requestId);
                        this.$drawFrame(true);
                        this.config && this.config.onSeeking && this.config.onSeeking(this.currentTime);
                        return;
                    }
                    this.currentTime = Math.floor(this.currentTime / this.frameStep) * this.frameStep;
                }
            }
            const frameTime = Math.min(this.currentTime + direction * this.frameStep, this.lastFrameTime);
            const currentIndex = Math.round(frameTime / this.frameStep);
            if (currentIndex < 0 || currentIndex >= this.frameList.length)
                return;
            if (frameTime >= 0) {
                if (frameTime <= this.lastFrameTime - this.frameStep) {
                    this.$render(direction);
                    this.currentTime = Math.max(0, frameTime);
                }
                else {
                    if (this.currentTime <= this.lastFrameTime - this.frameStep) {
                        this.$render(direction);
                        this.currentTime = Math.min(frameTime, this.lastFrameTime);
                    }
                }
            }
        }
        else { // 没有预览
            this.currentTime += direction * this.frameStep;
            this.currentTime = this.currentTime > this.duration - 5 ? this.duration - 5 : this.currentTime; // 结尾预留5s
            this.currentTime = this.currentTime < 0 ? 0 : this.currentTime;
        }
        this.config && this.config.onSeeking && this.config.onSeeking(this.currentTime);
    }
}
/**
 * 抽帧预览配置定义
 * @typedef IPreviewConfig
 * @property {Array<string>} previews 预览图片数据
 * @property {HTMLElement} domNode 预览框dom节点
 * @property {ZteData} zteData 中兴预览数据
 * @property {number} [canvasWidth] canvas长
 * @property {number} [frameCol] 大图中的帧列数
 * @property {number} [frameRow] 大图中的帧行数
 * @property {number} [frameWidth] 一帧的长度
 * @property {number} [frameHeight] 一帧的高度
 * @property {number} [focusScale] 焦点帧的缩放比例
 * @property {number} [frameGap] 帧左右间隔
 * @property {number} [focusGap] 焦点帧左右间距
 * @property {number} [duration] 片长
 * @property {number} [easing] 缓动步长
 * @property {number} [fontSize] 显示的文字尺寸
 * @property {string} [color] 字体的颜色
 * @property {Function} [onSeeking] seek时的回调函数
 */
/**
 * 获取FrameView实例
 * @param {IPreviewConfig} config 预览配置
 * @returns {FrameView} FrameView实例
 * @example
 * // 获取实例
 * let p = createFrameView({
 *    zteData: zteData,// 中兴预览数据
 *    previews: previews,// 华为预览图数组
 *    domNode: $('canvas'),
 *    onSeeking: function (currentTime) {
 *      console.log(currentTime);
 *    }
 * });
 * p.seek(-1);// 向左抽帧
 * p.seek(1);// 向右抽帧
 */
export function createFrameView(config) {
    return new FrameView(config);
}
